﻿using System;
using System.Timers;
using System.IO;
using System.Collections.Generic;
using System.Collections;
using System.Net;
using System.Net.Sockets;

namespace Network {

    public enum SocketState {
        StateInit,
        StateConnecting,
        StateConnected,
        StateShutDown,
        StateSocketError,
    }

    public class CSocket {
        private const int BufferLen = 40960;

        private Socket m_socket;
        private SocketState m_state;
        private SocketError m_errorCode;

        private byte[] m_sendBuffer = new byte[BufferLen];
        private int m_sendHead;
        private int m_sendTail;
        private byte[] m_recvBuffer = new byte[BufferLen];
        private int m_recvHead;
        private int m_recvTail;

        private bool m_isSending;
        private bool m_isRecving;

        public CSocket() {
            m_state = SocketState.StateInit;
            ClearSocketErrorCode();
        }

        ~CSocket() {
            m_sendBuffer = null;
            m_recvBuffer = null;
        }

        public bool Connected {
            get {
                if ( m_socket != null ) {
                    return m_state == SocketState.StateConnected;
                }
                return false;
            }
        }

        public SocketState GetState() {
            return m_state;
        }

        public bool IsErrorState() {
            return m_state == SocketState.StateSocketError;
        }

        public SocketError SocketErrorCode {
            get { return m_errorCode; }
        }

        public void ConnectSocket( String server, int port ) {
            try {
                m_socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );

                m_sendHead = m_sendTail = 0;
                m_recvHead = m_recvTail = 0;
                m_isSending = m_isRecving = false;
                m_state = SocketState.StateConnecting;
                ClearSocketErrorCode();
                IPAddress[] iP = Dns.GetHostAddresses( server );
                try {
                    m_socket.Connect( iP, port );
                    m_state = SocketState.StateConnected;
                } catch ( SocketException e ) {
                    m_state = SocketState.StateSocketError;
                    m_errorCode = (SocketError)e.ErrorCode;
                }

                /*Common.ULogFile.sharedInstance.LogEx("Net", "NetState:[{0}] ConnectSocketAddressFamily: [{1}] server: [{2}] port: [{3}]",
                    m_state.ToString(), 
                    iP[0].AddressFamily.ToString(), 
                    server,
                    port);*/
            } catch ( SocketException ex ) {
                SetErrorState( ex.SocketErrorCode );
            }
        }

        #region Member access
        public Socket socket {
            get {
                return m_socket;
            }
            set {
                m_socket = value;
            }
        }
        public SocketState socketState {
            get {
                return m_state;
            }
            set {
                m_state = value;
            }
        }
        public SocketError socketError {
            get {
                return m_errorCode;
            }
            set {
                m_errorCode = value;
            }
        }
        #endregion

        #region Sync Conn
        public void ConnectSocket( String server, int port, Action<bool> requestCallback )
        {
            try {
                m_socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );

                m_sendHead = m_sendTail = 0;
                m_recvHead = m_recvTail = 0;
                m_isSending = m_isRecving = false;
                m_state = SocketState.StateConnecting;
                ClearSocketErrorCode();
                IPAddress[] iP = Dns.GetHostAddresses( server );
                try {
                    m_state = SocketState.StateConnecting;
                    m_socket.BeginConnect( iP, port,
                        ( ar ) =>
                        {
                            ConnectComplete( ar, requestCallback );
                        },
                        null );
                } catch ( SocketException e ) {
                    m_state = SocketState.StateSocketError;
                    m_errorCode = (SocketError)e.ErrorCode;
                }
                //m_socket.BeginConnect( iP, port, ConnectComplete, null );
            } catch ( SocketException ex ) {
                SetErrorState( ex.SocketErrorCode );
            }
        }
        #endregion

        public void Shutdown() {
            m_state = SocketState.StateShutDown;
            try {
                if ( m_socket != null && m_errorCode != SocketError.NotConnected ) {
                    m_socket.Shutdown( SocketShutdown.Both );
                    m_socket.Close();
                }
                m_socket = null;
            } catch ( SocketException ) {
                m_state = SocketState.StateShutDown;
                m_socket = null;
            }
        }

        public bool Send( byte[] buf, int iOffet, int size ) {
            if ( m_sendTail + size > BufferLen ) {
                Buffer.BlockCopy( m_sendBuffer, m_sendHead, m_sendBuffer, 0, m_sendTail - m_sendHead );
                m_sendTail -= m_sendHead;
                m_sendHead = 0;
            }
            if ( m_sendTail + size > BufferLen ) {
                return false;
            }
            Buffer.BlockCopy( buf, iOffet, m_sendBuffer, m_sendTail, size );
            m_sendTail += size;
            return true;
        }

        public int Recv( byte[] buf, int iOffset, int maxSize ) {
            int iReadSize = Math.Min( maxSize, m_recvTail - m_recvHead );
            if ( iReadSize != 0 ) {
                Buffer.BlockCopy( m_recvBuffer, m_recvHead, buf, iOffset, iReadSize );
                m_recvHead += iReadSize;
            }
            return iReadSize;
        }

        public void Update() {
            if ( m_state == SocketState.StateConnected ) {
                ProcessSend();
                ProcessRecv();
            }
        }

        private void ProcessRecv() {
            try {
                if ( null != m_socket && !m_isRecving ) {
                    m_isRecving = true;
                    if ( BufferLen == m_recvTail ) {
                        Buffer.BlockCopy( m_recvBuffer, m_recvHead, m_recvBuffer, 0, m_recvTail - m_recvHead );
                        m_recvTail -= m_recvHead;
                        m_recvHead = 0;
                    }
                    m_socket.BeginReceive( m_recvBuffer, m_recvTail, BufferLen - m_recvTail, 0, RecvCallBack, 0 );
                }
            } catch ( SocketException ex ) {
                SetErrorState( ex.SocketErrorCode );
            }
        }

        private void ProcessSend() {
            try {
                if ( null != m_socket && m_sendHead != m_sendTail && !m_isSending ) {
                    m_isSending = true;
                    m_socket.BeginSend( m_sendBuffer, m_sendHead, m_sendTail - m_sendHead, 0, SendCallBack, 0 );
                }
            } catch ( SocketException ex ) {
                SetErrorState( ex.SocketErrorCode );
            }
        }

        private void ConnectComplete( IAsyncResult ar, Action<bool> userCallback ) {
            try {
                if ( null != m_socket ) {
                    m_socket.EndConnect( ar );
                    m_state = SocketState.StateConnected;
                    if(userCallback != null)
                    {
                        userCallback( true );
                    }
                }
            } catch ( SocketException e ) {
                m_state = SocketState.StateSocketError;
                m_errorCode = (SocketError)e.ErrorCode;
                if(userCallback != null)
                {
                    userCallback( false );
                }
            }
        }

        private void RecvCallBack( IAsyncResult ar ) {
            try {
                if ( null != m_socket ) {
                    int iRead = m_socket.EndReceive( ar );
                    if ( iRead > 0 ) {
                        m_recvTail += iRead;
                    }
                    m_isRecving = false;
                }
            } catch ( SocketException ex ) {
                SetErrorState( ex.SocketErrorCode );
            }
        }

        private void SendCallBack( IAsyncResult ar ) {
            try {
                if ( null != m_socket ) {
                    int iSend = m_socket.EndSend( ar );
                    m_sendHead += iSend;
                    m_isSending = false;
                }
            } catch ( SocketException ex ) {
                SetErrorState( ex.SocketErrorCode );
            }
        }

        private void ClearSocketErrorCode() {
            m_errorCode = SocketError.Success;
        }

        private void SetErrorState( SocketError errcode ) {
            if ( !IsErrorState() ) {
                m_state = SocketState.StateSocketError;
                m_errorCode = errcode;
            }
        }
    }
}
